package utd.cs6322.cluster.visualization;

/**
 * Extension of Jeffrey's original GraphViewDemo of the prefuse library
 * which implements a generic ToolTipControl to display additional
 * informations of a node. Don't miss to be astonished by the fact that the
 * tooltip works in both displays ...
 * More meant as a base for discussion rather than a finalized approach.
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 * @author <a href="http://goosebumps4all.net/3all">martin dudek</a>
 * 
 */

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Rectangle2D;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.ToolTipManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.filter.GraphDistanceFilter;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.activity.Activity;
import prefuse.controls.ControlAdapter;
import prefuse.controls.DragControl;
import prefuse.controls.FocusControl;
import prefuse.controls.NeighborHighlightControl;
import prefuse.controls.PanControl;
import prefuse.controls.WheelZoomControl;
import prefuse.controls.ZoomControl;
import prefuse.controls.ZoomToFitControl;
import prefuse.data.Graph;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.io.GraphMLReader;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.GraphLib;
import prefuse.util.GraphicsLib;
import prefuse.util.display.DisplayLib;
import prefuse.util.display.ItemBoundsListener;
import prefuse.util.force.ForceSimulator;
import prefuse.util.io.IOLib;
import prefuse.util.ui.JForcePanel;
import prefuse.util.ui.JValueSlider;
import prefuse.util.ui.UILib;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;

public class Example extends JPanel {

    private static final String graph = "graph";
    private static final String nodes = "graph.nodes";
    private static final String edges = "graph.edges";

    private Visualization m_vis;

    public Example(Graph g, String label) {

	// create a new, empty visualization for our data
	m_vis = new Visualization();

	// --------------------------------------------------------------------
	// set up the renderers

	LabelRenderer tr = new LabelRenderer();
	tr.setRoundedCorner(8, 8);
	m_vis.setRendererFactory(new DefaultRendererFactory(tr));

	// --------------------------------------------------------------------
	// register the data with a visualization

	// adds graph to visualization and sets renderer label field
	setGraph(g, label);

	// fix selected focus nodes
	TupleSet focusGroup = m_vis.getGroup(Visualization.FOCUS_ITEMS); 
	focusGroup.addTupleSetListener(new TupleSetListener() {
	    public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem)
	    {
		for ( int i=0; i<rem.length; ++i )
		    ((VisualItem)rem[i]).setFixed(false);
		for ( int i=0; i<add.length; ++i ) {
		    ((VisualItem)add[i]).setFixed(false);
		    ((VisualItem)add[i]).setFixed(true);
		}
		if ( ts.getTupleCount() == 0 ) {
		    ts.addTuple(rem[0]);
		    ((VisualItem)rem[0]).setFixed(false);
		}
		m_vis.run("draw");
	    }
	});



	// --------------------------------------------------------------------
	// create actions to process the visual data

	int hops = 30;
	final GraphDistanceFilter filter = new GraphDistanceFilter(graph, hops);

	ColorAction fill = new ColorAction(nodes, 
		VisualItem.FILLCOLOR, ColorLib.rgb(200,200,255));
	fill.add(VisualItem.FIXED, ColorLib.rgb(255,100,100));
	fill.add(VisualItem.HIGHLIGHT, ColorLib.rgb(255,200,125));

	ActionList draw = new ActionList();
	draw.add(filter);
	draw.add(fill);
	draw.add(new ColorAction(nodes, VisualItem.STROKECOLOR, 0));
	draw.add(new ColorAction(nodes, VisualItem.TEXTCOLOR, ColorLib.rgb(0,0,0)));
	draw.add(new ColorAction(edges, VisualItem.FILLCOLOR, ColorLib.gray(200)));
	draw.add(new ColorAction(edges, VisualItem.STROKECOLOR, ColorLib.gray(200)));

	ActionList animate = new ActionList(Activity.INFINITY);
	animate.add(new ForceDirectedLayout(graph));
	animate.add(fill);
	animate.add(new RepaintAction());

	// finally, we register our ActionList with the Visualization.
	// we can later execute our Actions by invoking a method on our
	// Visualization, using the name we've chosen below.
	m_vis.putAction("draw", draw);
	m_vis.putAction("layout", animate);

	m_vis.runAfter("draw", "layout");


	// --------------------------------------------------------------------
	// set up a display to show the visualization

	Display display = new Display(m_vis);
	display.setSize(700,700);
	display.pan(350, 350);
	display.setForeground(Color.GRAY);
	display.setBackground(Color.WHITE);

	// main display controls
	display.addControlListener(new FocusControl(1));
	display.addControlListener(new DragControl());
	display.addControlListener(new PanControl());
	display.addControlListener(new ZoomControl());
	display.addControlListener(new WheelZoomControl());
	display.addControlListener(new ZoomToFitControl());
	display.addControlListener(new NeighborHighlightControl());

	//MAD - adding the brand new control to the display
	String tt[] = { "My precious name:","My delightful gender: "};
	String ff[] = { "name","gender"};

	DemoControlToolTip dctt = new DemoControlToolTip(tt,ff);
	display.addControlListener(dctt);

	// overview display
	Display overview = new Display(m_vis);
	overview.setSize(290,290);
	overview.addItemBoundsListener(new FitOverviewListener());

	overview.addControlListener(dctt);

	display.setForeground(Color.GRAY);
	display.setBackground(Color.WHITE);

	// --------------------------------------------------------------------        
	// launch the visualization

	// create a panel for editing force values
	ForceSimulator fsim = ((ForceDirectedLayout)animate.get(0)).getForceSimulator();
	JForcePanel fpanel = new JForcePanel(fsim);

	JPanel opanel = new JPanel();
	opanel.setBorder(BorderFactory.createTitledBorder("Overview"));
	opanel.setBackground(Color.WHITE);
	opanel.add(overview);

	final JValueSlider slider = new JValueSlider("Distance", 0, hops, hops);
	slider.addChangeListener(new ChangeListener() {
	    public void stateChanged(ChangeEvent e) {
		filter.setDistance(slider.getValue().intValue());
		m_vis.run("draw");
	    }
	});
	slider.setBackground(Color.WHITE);
	slider.setPreferredSize(new Dimension(300,30));
	slider.setMaximumSize(new Dimension(300,30));

	Box cf = new Box(BoxLayout.Y_AXIS);
	cf.add(slider);
	cf.setBorder(BorderFactory.createTitledBorder("Connectivity Filter"));
	fpanel.add(cf);

	fpanel.add(opanel);

	fpanel.add(Box.createVerticalGlue());

	// create a new JSplitPane to present the interface
	JSplitPane split = new JSplitPane();
	split.setLeftComponent(display);
	split.setRightComponent(fpanel);
	split.setOneTouchExpandable(true);
	split.setContinuousLayout(false);
	split.setDividerLocation(700);

	// now we run our action list
	m_vis.run("draw");

	add(split);
    }

    public void setGraph(Graph g, String label) {
	// update labeling
	DefaultRendererFactory drf = (DefaultRendererFactory)
	m_vis.getRendererFactory();
	((LabelRenderer)drf.getDefaultRenderer()).setTextField(label);

	// update graph
	m_vis.removeGroup(graph);
	VisualGraph vg = m_vis.addGraph(graph, g);
	m_vis.setValue(edges, null, VisualItem.INTERACTIVE, Boolean.FALSE);
	VisualItem f = (VisualItem)vg.getNode(0);
	m_vis.getGroup(Visualization.FOCUS_ITEMS).setTuple(f);
	f.setFixed(false);
    }

    // ------------------------------------------------------------------------
    // Main and demo methods

    public static void main(String[] args) {
	UILib.setPlatformLookAndFeel();

	// create graphview
	//String datafile = null;
	//String label = "label";

	String datafile = "http://prefuse.org/doc/manual/introduction/example/socialnet.xml";
	String label = "name";

	if ( args.length > 1 ) {
	    datafile = args[0];
	    label = args[1];
	}

	JFrame frame = demo(datafile, label);
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static JFrame demo() {
	return demo((String)null, "label");
    }

    public static JFrame demo(String datafile, String label) {
	Graph g = null;
	if ( datafile == null ) {
	    g = GraphLib.getGrid(15,15);
	    label = "label";
	} else {
	    try {
		g = new GraphMLReader().readGraph(datafile);
	    } catch ( Exception e ) {
		e.printStackTrace();
		System.exit(1);
	    }
	}
	return demo(g, label);
    }

    public static JFrame demo(Graph g, String label) {
	final Example view = new Example(g, label);

	// set up menu
	JMenu dataMenu = new JMenu("Data");
	dataMenu.add(new OpenGraphAction(view));
	dataMenu.add(new GraphMenuAction("Grid","ctrl 1",view) {
	    protected Graph getGraph() {
		return GraphLib.getGrid(15,15);
	    }
	});
	dataMenu.add(new GraphMenuAction("Clique","ctrl 2",view) {
	    protected Graph getGraph() {
		return GraphLib.getClique(10);
	    }
	});
	dataMenu.add(new GraphMenuAction("Honeycomb","ctrl 3",view) {
	    protected Graph getGraph() {
		return GraphLib.getHoneycomb(5);
	    }
	});
	dataMenu.add(new GraphMenuAction("Balanced Tree","ctrl 4",view) {
	    protected Graph getGraph() {
		return GraphLib.getBalancedTree(3,5);
	    }
	});
	dataMenu.add(new GraphMenuAction("Diamond Tree","ctrl 5",view) {
	    protected Graph getGraph() {
		return GraphLib.getDiamondTree(3,3,3);
	    }
	});
	JMenuBar menubar = new JMenuBar();
	menubar.add(dataMenu);

	// launch window
	JFrame frame = new JFrame("p r e f u s e  |  g r a p h v i e w");
	frame.setJMenuBar(menubar);
	frame.setContentPane(view);
	frame.pack();
	frame.setVisible(true);

	frame.addWindowListener(new WindowAdapter() {
	    public void windowActivated(WindowEvent e) {
		view.m_vis.run("layout");
	    }
	    public void windowDeactivated(WindowEvent e) {
		view.m_vis.cancel("layout");
	    }
	});

	return frame;
    }


    // ------------------------------------------------------------------------

    /**
     * Swing menu action that loads a graph into the graph viewer.
     */
    public abstract static class GraphMenuAction extends AbstractAction {
	private Example m_view;
	public GraphMenuAction(String name, String accel, Example view) {
	    m_view = view;
	    this.putValue(AbstractAction.NAME, name);
	    this.putValue(AbstractAction.ACCELERATOR_KEY,
		    KeyStroke.getKeyStroke(accel));
	}
	public void actionPerformed(ActionEvent e) {
	    m_view.setGraph(getGraph(), "label");
	}
	protected abstract Graph getGraph();
    }

    public static class OpenGraphAction extends AbstractAction {
	private Example m_view;

	public OpenGraphAction(Example view) {
	    m_view = view;
	    this.putValue(AbstractAction.NAME, "Open File...");
	    this.putValue(AbstractAction.ACCELERATOR_KEY,
		    KeyStroke.getKeyStroke("ctrl O"));
	}
	public void actionPerformed(ActionEvent e) {
	    Graph g = IOLib.getGraphFile(m_view);
	    if ( g == null ) return;
	    String label = getLabel(m_view, g);
	    if ( label != null ) {
		m_view.setGraph(g, label);
	    }
	}
	public static String getLabel(Component c, Graph g) {
	    // get the column names
	    Table t = g.getNodeTable();
	    int  cc = t.getColumnCount();
	    String[] names = new String[cc];
	    for ( int i=0; i<cc; ++i )
		names[i] = t.getColumnName(i);

	    // where to store the result
	    final String[] label = new String[1];

	    // -- build the dialog -----
	    // we need to get the enclosing frame first
	    while ( c != null && !(c instanceof JFrame) ) {
		c = c.getParent();
	    }
	    final JDialog dialog = new JDialog(
		    (JFrame)c, "Choose Label Field", true);

	    // create the ok/cancel buttons
	    final JButton ok = new JButton("OK");
	    ok.setEnabled(false);
	    ok.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    dialog.setVisible(false);
		}
	    });
	    JButton cancel = new JButton("Cancel");
	    cancel.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    label[0] = null;
		    dialog.setVisible(false);
		}
	    });

	    // build the selection list
	    final JList list = new JList(names);
	    list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
	    list.getSelectionModel().addListSelectionListener(
		    new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent e) {
			    int sel = list.getSelectedIndex(); 
			    if ( sel >= 0 ) {
				ok.setEnabled(true);
				label[0] = (String)list.getModel().getElementAt(sel);
			    } else {
				ok.setEnabled(false);
				label[0] = null;
			    }
			}
		    });
	    JScrollPane scrollList = new JScrollPane(list);

	    JLabel title = new JLabel("Choose a field to use for node labels:");

	    // layout the buttons
	    Box bbox = new Box(BoxLayout.X_AXIS);
	    bbox.add(Box.createHorizontalStrut(5));
	    bbox.add(Box.createHorizontalGlue());
	    bbox.add(ok);
	    bbox.add(Box.createHorizontalStrut(5));
	    bbox.add(cancel);
	    bbox.add(Box.createHorizontalStrut(5));

	    // put everything into a panel
	    JPanel panel = new JPanel(new BorderLayout());
	    panel.add(title, BorderLayout.NORTH);
	    panel.add(scrollList, BorderLayout.CENTER);
	    panel.add(bbox, BorderLayout.SOUTH);
	    panel.setBorder(BorderFactory.createEmptyBorder(5,2,2,2));

	    // show the dialog
	    dialog.setContentPane(panel);
	    dialog.pack();
	    dialog.setLocationRelativeTo(c);
	    dialog.setVisible(true);
	    dialog.dispose();

	    // return the label field selection
	    return label[0];
	}
    }

    public static class FitOverviewListener implements ItemBoundsListener {
	private Rectangle2D m_bounds = new Rectangle2D.Double();
	private Rectangle2D m_temp = new Rectangle2D.Double();
	private double m_d = 15;
	public void itemBoundsChanged(Display d) {
	    d.getItemBounds(m_temp);
	    GraphicsLib.expand(m_temp, 25/d.getScale());

	    double dd = m_d/d.getScale();
	    double xd = Math.abs(m_temp.getMinX()-m_bounds.getMinX());
	    double yd = Math.abs(m_temp.getMinY()-m_bounds.getMinY());
	    double wd = Math.abs(m_temp.getWidth()-m_bounds.getWidth());
	    double hd = Math.abs(m_temp.getHeight()-m_bounds.getHeight());
	    if ( xd>dd || yd>dd || wd>dd || hd>dd ) {
		m_bounds.setFrame(m_temp);
		DisplayLib.fitViewToBounds(d, m_bounds, 0);
	    }
	}
    }

    /**
     * MAD
     * Demo control which shows one way of creating a more extended tool tip.
     * Kept generic and meant as a base for individual extensions 
     * 
     * @author <a href="http://goosebumps4all.net/3all">martin dudek</a>
     *
     */

    public class DemoControlToolTip extends ControlAdapter {

	private String[] fields;
	private String[] texts;
	private StringBuffer sbuf;



	int dismissDelay = Integer.MAX_VALUE;

	public DemoControlToolTip(String[] texts, String[] fields) {
	    this.texts = texts;
	    this.fields = fields;
	    sbuf = new StringBuffer();

	    ToolTipManager.sharedInstance().setInitialDelay(276);

	}

	public void itemEntered(VisualItem vi, MouseEvent e) {

	    String value;

	    ToolTipManager.sharedInstance().setDismissDelay(dismissDelay);

	    sbuf.delete(0, sbuf.length());

	    sbuf.append("<html><table>");

	    boolean someInfo = false;

	    for (int i = 0; i < fields.length; i++) {
		if (vi.canGetString(fields[i])) {
		    value = vi.getString(fields[i]);
		    if (value != null && value.length() > 0) {
			sbuf.append("<tr valign='top'><td><b>");
			sbuf.append(texts[i] + "</b></td><td>");
			sbuf.append(value);
			someInfo = true;
		    }
		    sbuf.append("</td></tr>");
		}
	    }

	    if (someInfo) {
		sbuf.append("</table>");
		sbuf.append("<hr size=1 width=\"97%\"><div align=\"right\"><font color=\"#999999\">esc to close&nbsp;</font></div>");
		sbuf.append("</html>");

		Display disp = (Display) e.getSource(); //the display on which the event occured
		disp.setToolTipText(sbuf.toString());
	    }

	}

	public void itemExited(VisualItem item, MouseEvent e) {
	    Display disp = (Display) e.getSource();
	    disp.setToolTipText(null);
	}

    } // end of class DemoControlToolTip

} // end of class GraphView